; -------------------------------------------------------------
; Firmware for the PIC16F688 Based DMX DCSSR
; (c) 2012 Joshua Moyerman
;
; With input from Andrew Williams, Just Some Guy Productions
; Based On
; JECPWM_3.asm											
; (c) 2007 John Chapman, Engineering Solutions Inc
; Inspired somewhat by Microchip's application notes
; for receiving DMX and generating software PWM.
; Note: The reference designs for these pixels are distributed 
; under a Creative Commons license Attribution-ShareAlike 2.5

	list      p=16F688        ; list directive to define processor
	#include <p16F688.inc>   ; REMOVE THE EXTRA SPACES BETWEEN < and > - processor specific variable definitions
	errorlevel  -302          ; suppress message 302 from list file

; Here include Configuration settings
	__CONFIG _FCMEN_OFF & _IESO_OFF & _BOD_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_OFF & _WDT_ON & _INTOSCIO
; OSC = INTOSCIO
; Watchdog = Disabled
; Power up Timer  = Disabled
; MCLR = Reset
; Brownout = Enabled, SBODEN disabled
; Switchover = Disabled
; Failsave = Disabled
; Code = As needed
; EEPROM = As needed

;  --------- Define values here  ---------
#define BOARD DMX_DC9
#define DMX_START_ADDRESS D'1'	; Set the DMX value for the controller
#define DEFAULTVALUE 0x00	; Value that all LED's will turn on to when
							; controller starts

#include <DMX_DCSSR.inc>

; --------- Variable Definitions --------- 
	CBLOCK 0x20
	wsave		; ISR Save
	ssave	
	psave

	pinsa		; Port Values
	pinsc
	
	IntCount	; Counter for PWM routine
    timer       ; Counter used for delay routines
    count       ; Another Cou-nter for delay routines
	
	temp		; Timer Var

    dmxstat         ; DMX Program routine vars
    dmxproghigh
    dmxproglow
    pskiphigh
    pskiplow
    newdmxhigh
    newdmxlow

	ENDC

	; Put these where any bank can access them
	CBLOCK		0xF0
	dmxhighbyte	; Start address
	dmxlowbyte
	
	skiphigh	; Counters used to wait for address	
	skiplow	
	ENDC
	
; CHECK RAM ADDRESSES FOR 16F1823
; Auto-detect number of channels defined	
#if CHANNELS >= 1
	CBLOCK      0xDE
	ch1			
	ch1new
	ENDC
#endif
#if CHANNELS >= 2
	CBLOCK      0xE0
	ch2
	ch2new
	ENDC
#endif
#if CHANNELS >= 3
	CBLOCK      0XE2
	ch3
	ch3new
	ENDC
#endif
#if CHANNELS >= 4
	CBLOCK      0XE4
	ch4
	ch4new
	ENDC
#endif
#if CHANNELS >= 5
	CBLOCK      0XE6
	ch5
	ch5new
	ENDC
#endif
#if CHANNELS >= 6
	CBLOCK      0XE8
	ch6
	ch6new
	ENDC
#endif
#if CHANNELS >= 7
	CBLOCK      0XEA
	ch7
	ch7new
	ENDC
#endif
#if CHANNELS >= 8
	CBLOCK      0XEC
	ch8
	ch8new
	ENDC
#endif
#if CHANNELS >= 9
	CBLOCK      0XEE
	ch9
	ch9new
	ENDC
#endif

; --------- End Var Defs ---------
	ORG 0x000			; Main Program

	clrf PCLATH
	goto start

	ORG 0x004			; Interrupt handler goes here

pwmdrive
 	movwf	wsave		; Store the usual bits when interrupting
 	swapf	STATUS, w
 	clrf	STATUS
 	movwf	ssave
 	movf	PCLATH, w
 	movwf	psave

; ------- Actual Interrupt Routine Here --------------------

 	movlw 	D'200'		; TMR0 will roll over every 40.0 uS
 	movwf 	TMR0		; which allows for 97.65 Hz PWM frequency
						; Nice thing here is that even if we're receiving
						; full-on DMX (44, 512-byte frames per second)
						; the PWM routine can easily keep up.  So some 
						; exciting effects should be possible.

DecIntCount						
	decfsz	IntCount,F		; Decrement IntCount register.  If it's zero, reset 
	goto	ch1check		; the PWM routine.  Otherwise, check each PWM value

PWMReset				; We've been through 256 cycles of PWM, so it's time
	clrf	pinsa		; to reset everything.
	movf	pinsa,w		; Clear the Ports
	movwf	PORTA
	clrf	pinsc
	movf	pinsc,w
	movwf	PORTC
	
	movlw	D'255'		; Reset the counter to allow 255 values per channel 
	movwf	IntCount	; Also, since we're resetting, it's time to transfer
    movf  	ch1new,w	; the *new variables (which came from the DMX routine)
    movwf 	ch1			; to the working PWM variables.
    movf  	ch2new,w	; If the transfer takes place at any time *other* than
    movwf 	ch2			; during a PWM reset, the LEDs flicker unflatteringly
    movf  	ch3new,w	
    movwf	ch3			
	movf	ch4new,w
	movwf	ch4
	movf	ch5new,w
	movwf	ch5
	movf	ch6new,w
	movwf	ch6
	movf	ch7new,w
	movwf	ch7
	movf	ch8new,w
	movwf	ch8
	movf	ch9new,w
	movwf	ch9

    goto	pwmexit			; Exit ISR

; Here we compare each of the duty cycles to the IntCount.  If the values
; match, the corresponding pin is driven high.  It's a bit counter-intuitive
; but it works.  Note that if a value of 255 is received, it won't work.  So 
; the DMX routine limits PWM values to [0 254].  Which is good enough.

ch1check
	movf	ch1,w
	subwf	IntCount,w
	btfss	STATUS,Z	; Are they equal?
	goto	ch2check	; Note that the *val variables are used only within
	bsf		CH1VAR,CH1PIN		; the PWM routine.  The actual DMX data is stored	
ch2check				; in the *new variables, and transferred to these
	movf	ch2,w		; working variables only when the PWM rolls over
	subwf	IntCount,w			         
	btfss	STATUS,Z	; If the values are equal, set the bit (which turns         
	goto	ch3check	; on the corresponding drive pin)
	bsf		CH2VAR,CH2PIN
ch3check
	movf	ch3,w
	subwf	IntCount,w			         
	btfss	STATUS,Z  
	goto	ch4check
	bsf		CH3VAR,CH3PIN
ch4check
	movf	ch4,w
	subwf	IntCount,w			         
	btfss	STATUS,Z  
	goto	ch5check
	bsf		CH4VAR,CH4PIN
ch5check
	movf	ch5,w
	subwf	IntCount,w
	btfss	STATUS,Z
	goto	ch6check
	bsf		CH5VAR,CH5PIN
ch6check
	movf	ch6,w
	subwf	IntCount,w
	btfss	STATUS,Z
	goto	ch7check
	bsf		CH6VAR,CH6PIN
ch7check
	movf	ch7,w
	subwf	IntCount,w
	btfss	STATUS,Z
	goto	ch8check
	bsf		CH7VAR,CH7PIN
ch8check
	movf	ch8,w
	subwf	IntCount,w
	btfss	STATUS,Z
	goto	ch9check
	bsf		CH8VAR,CH8PIN
ch9check
	movf	ch9,w
	subwf	IntCount,w
	btfsc	STATUS,Z             
	bsf		CH9VAR,CH9PIN
	
	movf	pinsa,w		; Move the values out to PORTA and PORTC
	movwf	PORTA		 
	movf	pinsc,w
	movwf	PORTC

pwmexit
 	BCF    INTCON,T0IF      ; Clear the TMR0 interrupt flag
 	movf	psave,w			; Restore whatever was happening prior
 	movwf	PCLATH			; to the interrupt and get back to
 	swapf	ssave,w			; gathering DMX data.
 	movwf	STATUS
 	swapf	wsave,f
 	swapf	wsave,w
 
 	retfie				; Back to gathering DMX data

; ---------- Main Program Starts Here ----------------------------
start
	call	chipinit		; Initialize pins, oscillator, etc
	call	meminit			; Clears Values (defaults controller to off)
	call	defaultdmxread	; reads coded value for start address
	call 	dmxread

	call	loop
	call 	loop
#ifdef PROG_PULL_UP
	btfss	PROG_PORT,PROG_PIN		; Check for Jumper (pull up configuration)
#endif

#ifdef PROG_PULL_DOWN
    btfsc   PROG_PORT,PROG_PIN      ; Check for Program Jumper (pull down)
#endif
    goto    dmxprogram              ; Go to program routine if jumper set

	call	pwminit			; Initialize TIMER0 and enable TMRO interrupts
	call 	rxinit			; Set up the EUSART to receive DMX at 250,000 baud
	BANK0

dmxcapture
	clrwdt					; Clear Watchdog Counter
	movf	dmxhighbyte,w	; Skipcounter is used to detmine how many
	movwf	skiphigh		; received data bytes are skipped before the RGB
	movf	dmxlowbyte,w	; data is collected.  Load skipcounter with
	movwf	skiplow			; the DMX address from above...

	movf  	skiplow,f		; ... then decrement it by one
  	btfsc	STATUS,Z		; so we know how many channels to ignore before the
   	decf  	skiphigh,f		; useful data arrives.  We'll see more of the 
  	decf  	skiplow,f		; skipcounter a bit farther down the page.

waitbreak
	btfsc	PIR1,RCIF		; Here we're waiting to see if a break occurs
	movf	RCREG,w			; in the data signal.  Since we're *only*
	btfss	RCSTA,FERR		; receiving DMX, anything which generates a 
	goto	waitbreak		; framing error in the EUSART will count as a break.
	movf	RCREG,w			; If a byte is received correctly, dump it and loop				
					; back until we get the error we need
					; without being able to synchronzie to the break signal
					; there's no way to extract valid DMX data
waitforstart
	btfss	PIR1,RCIF		; Now that a break signal is detected, 
	goto 	waitforstart		; loop until a new byte is received *without*
	btfsc	RCSTA,FERR		; a framing error.  If all is well AND the
	goto 	waitforstart		; new byte is zero (which means the start code
	movf	RCREG,w			; is also zero, it's okay to begin gethering channel
					; data

	; RIGHT NOW WE'RE NOT TESTING FOR A ZERO START CODE. THIS WILL BE CHANGED IN FUTURE
	; VERSIONS OF THE CODE.  BUYER BEWARE!

	movf	dmxhighbyte,1		; Here check to see if the highbyte is
	btfss	STATUS,Z		; zero. If it is,check to see if the	
	goto 	bytecapture		; lowbyte is 1.  If 1, grab the next three bytes
	movf	dmxlowbyte,w		; which come through.  If <> 1, go to the routine
	xorlw	D'1'			; which receives and discards bytes until the 
	btfsc	STATUS,Z		; DMX address has been reached.
	goto 	waitforch1		

bytecapture	

	btfss	PIR1,RCIF		; If we're here, it's because the start address is
	goto  	bytecapture		; greater than one. Hover until a byte is received.
	movf	RCREG,w			; Then, capture & move to 'w'...	
								
 	movf  	skiplow,f		; ...decrement the skip counter... 
  	btfsc	STATUS,Z	 	; (all sixteen bits of it)
   	decf  	skiphigh,f
  	decf  	skiplow,f
					; ...and see if we've reached the start address.
	movf	skiplow,1		; If the skip counter now equals zero, we know
	btfss	STATUS,Z		; that we need to gather the next three bytes	
	goto 	bytecapture		; and save them as RGB data.  If the counter is
	movf	skiphigh,1		; still nonzero, loop back and do it again.
	btfss	STATUS,Z
	goto	bytecapture

waitforch1
	btfss	PIR1,RCIF		; Wait until 'red' byte is received
	goto 	waitforch1		; once it arrives, store it in 'temp'
	movf	RCREG,w			; and call the 'maxcheck'  routine.  Since the		
	movwf	temp			; PWM code only works for values between [0 254]
	call	maxcheck		; maxcheck will set levels of 0xFF to 0xFE
	movwf	ch1new			; then store them in the proper bit bucket.
					; remember the the *new variables are converted
					; to *val variables when the PWM routine resets 
					; itself
waitforch2	
	btfss	PIR1,RCIF		; process is repeated for green data
	goto 	waitforch2
	movf	RCREG,w
	movwf	temp
	call	maxcheck
	movwf	ch2new

waitforch3				; ...and for blue data
	btfss	PIR1,RCIF		; It is assumed that there will be enough DMX
	goto 	waitforch3		; channels available to capture three bytes
	movf	RCREG,w			; per pixel.  For this reason, we don't check to
	movwf	temp			; see if the DMX string has timed out anywhere.
	call	maxcheck		; Rather, once all three bytes have been received,
	movwf 	ch3new			; the code loops back and waits for a new start code.
					; Were we to 'run out' of channel data in here somewhere
					; the code may behave strangely.  Caveat Emptor!
waitforch4	
	btfss	PIR1,RCIF
	goto 	waitforch4
	movf	RCREG,w
	movwf	temp
	call	maxcheck
	movwf	ch4new
waitforch5
	btfss	PIR1,RCIF
	goto 	waitforch5
	movf	RCREG,w
	movwf	temp
	call	maxcheck
	movwf	ch5new
waitforch6	
	btfss	PIR1,RCIF
	goto 	waitforch6
	movf	RCREG,w
	movwf	temp
	call	maxcheck
	movwf	ch6new
waitforch7
	btfss	PIR1,RCIF
	goto 	waitforch7
	movf	RCREG,w
	movwf	temp
	call	maxcheck
	movwf	ch7new
waitforch8
	btfss	PIR1,RCIF
	goto 	waitforch8
	movf	RCREG,w
	movwf	temp
	call	maxcheck
	movwf	ch8new
waitforch9
	btfss	PIR1,RCIF
	goto 	waitforch9
	movf	RCREG,w
	movwf	temp
	call	maxcheck
	movwf	ch9new

	goto 	dmxcapture		; Got all three bytes?  Repeat Ad Absurdum.

 
; ----------- Routines for Starting the Chip ---------

chipinit
	BANK0				; Memory bank 0
	movlw	b'00010111'					;Set WDT Prescaler
	movwf	WDTCON

	clrf	PORTC		; All PORTC Pins off
	clrf	PORTA		; All PORTA off
	clrf	CMCON0		; Comparators aren't used either
	clrf	CMCON1

	BANK1				; Switch to memory bank 1
	bcf	TRISA,0			; Red Drive Pin
	bcf	TRISA,1			; Green Drive Pin
	bcf TRISA,2
	bcf	TRISA,4			; Blue Drive Pin
	bcf TRISA,5
	bcf TRISC,0
	bcf TRISC,1
	bcf	TRISC,2
	bcf	TRISC,3
	bcf TRISC,4

	clrf	ANSEL			; Turn off A/D Converters
	bsf	OSCCON,6		; Set these three
	bsf	OSCCON,5		; bits to enable the
	bsf	OSCCON,4		; 8 MHz internal oscillator
				


	return

; ------------ Memory Init (Run at Startup) ------------ 
; Fills memory with default PWM Values
meminit
	BANK0
	movlw	DEFAULTVALUE
	movwf	ch1
	movwf	ch1new
	movwf	ch2
	movwf	ch2new
	movwf	ch3
	movwf	ch3new
	movwf	ch4
	movwf	ch4new
	movwf	ch5
	movwf	ch5new
	movwf	ch6
	movwf	ch6new
	movwf	ch7
	movwf	ch7new
	movwf	ch8
	movwf	ch8new	
	movwf	ch9
	movwf	ch9new
	return
	
	
rxinit
	BANK0
	bsf	TRISC,5			; PORTC.5 is input for DMX data to EUSART
	clrf	TXSTA			; Clear TXSTA register
	movlw 	B'10010000'		; Serial Port and continuous receive enabled
	movwf	RCSTA				
	movlw	D'1'			; for baud rate generator
	movwf	SPBRG			
	clrf	SPBRGH			; This combination assures 0% error when
	bsf	BAUDCTL,BRG16		; receiving DMX at 250,000 bits per second
	return				; the PLL makes it possible to grab such
					; high speed data without any error

pwminit
	movlw	B'10100000'		; Enable global and TMR0 interrupts
	movwf	INTCON
	BANK1
	clrf	OPTION_REG		; No prescaler for TMR0 needed
	BANK0	
	return



; ----------------- Other Subroutines Go Here

maxcheck

	; Processes value which is stored in W. 
	; New value is also in W when routine exits

	xorlw	D'255'			; Here we're checking to see if a received
	btfsc	STATUS,Z		; byte is greater than 254.  If it is,
	goto 	exit			; set it to 254.  If it's less than 254
	movf	temp,w			; leave it alone and the PWM routine will deal with
	return				; it shortly
exit
	movlw	D'254'
	return

; ----------------- dmxread -------
dmxread 
; read the dmx address from the eeprom
; Check to see if the address has ever been set.
        BANK1
        movlw   DMX_STATUS_BYTE     ; eeprom addr to read from
        movwf   EEADR               ; data address to read from
        bcf     EECON1, EEPGD       ; point to adata memory
        bsf     EECON1, RD          ; EE Read
#if	PROCESSOR == 16F688
        movf    EEDAT, w            ; Copy read value
#endif
		BANK0
        movwf   dmxstat         ; to status
        btfsc   dmxstat,0       ; is the bit clear?
        return                  ; We've never recieved a channel from
                                ; field programming, so lets not read
                                ; the EE since it has nothing useful.

	; Second read the dmxhighbyte
	BANK1
	movlw	0x00 		; eeprom addr
	movwf 	EEADR		; data address to read
	bcf	EECON1, EEPGD 	; point to adata memory
	bsf	EECON1, RD	; EE read
#if	PROCESSOR == 16F688
        movf    EEDAT, w            ; Copy read value
#endif
	BANK0
	movwf	dmxhighbyte	; now in dmxhighbyte mem location

	; now read the dmxlowbyte
	BANK1
	movlw 	0x01		; eeprom addr
	movwf 	EEADR		; data address to read
	bcf	EECON1, EEPGD 	; point to adata memory
	bsf	EECON1, RD	; EE read
#if	PROCESSOR == 16F688
        movf    EEDAT, w            ; Copy read value
	BANK0
	movwf	dmxlowbyte	; now in dmxlowbyte

	return

; defaultdmxread is called first, it reads the value hard coded in the assembly
; If there is a value in the eeprom, this will be overwritten.
defaultdmxread
; Processor independent
	movlw	high DMX_START_ADDRESS	; Don't set the DMX Address here. 
	movwf	dmxhighbyte
	movlw	low DMX_START_ADDRESS
	movwf	dmxlowbyte		
        return


; ----------------- dmxprogram -------
dmxprogram
	call rxinit
	bsf	CH1PORT,CH1PIN
                                
        movlw   D'0'            
        movwf   dmxproghigh     
        movlw   D'1'            
        movwf   dmxproglow      
                                
        BANK0

dmxprogcapture
		clrwdt
        movf    dmxproghigh,w   
        movwf   pskiphigh       
        movf    dmxproglow,w    
        movwf   pskiplow        

        movf    pskiplow,f      
        btfsc   STATUS,Z        
        decf    pskiphigh,f     
        decf    pskiplow,f      

progbreak
        btfsc   PIR1,RCIF       
        movf    RCREG,w         
        btfss   RCSTA,FERR      
        goto    progbreak       
        movf    RCREG,w         
                                
                                
                                
progstart
        btfss   PIR1,RCIF       
        goto    progstart	
        btfsc   RCSTA,FERR      
        goto    progstart	
        movf    RCREG,w         

        ; RIGHT NOW WE'RE NOT TESTING FOR A ZERO START CODE.
        ; THIS WILL BE CHANGED IN FUTURE
        ; VERSIONS OF THE CODE.  BUYER BEWARE!

        movf    dmxproghigh,1           
        btfss   STATUS,Z                
        goto    progcapture             
        movf    dmxproglow,w            
        xorlw   D'1'                    
        btfsc   STATUS,Z                
        goto    waitforhigh

progcapture
		clrwdt
        btfss   PIR1,RCIF               
        goto    progcapture             
        movf    RCREG,w                 
        movf    pskiplow,f              
        btfsc   STATUS,Z                
        decf    pskiphigh,f
        decf    pskiplow,f
                                        
        movf    pskiplow,1              
        btfss   STATUS,Z                
        goto    progcapture             
        movf    pskiphigh,1             
        btfss   STATUS,Z
        goto    progcapture

waitforhigh
        btfss   PIR1,RCIF               
        goto    waitforhigh             
        movf    RCREG,w
        movwf   newdmxhigh

waitforlow
        btfss   PIR1,RCIF
        goto    waitforlow
        movf    RCREG,w
        movwf   newdmxlow
	bsf 	CH6PORT,CH6PIN

; Program the high byte

; clear the EEIF interupt bit
	bcf	PIR1,EEIF
	BANK1
	movlw 	DMX_HIGH_BYTE           ;DMX HIGH ADDRESS
	movwf 	EEADR 			;Data Memory Address to write
	BANK0
	movf	newdmxhigh,w 				;
	BANK1
#if PROCESSOR == 16F688
	movwf	EEDAT 			;Data Memory Value to write
#endif
	bcf	EECON1, EEPGD 		;Point to DATA memory
	bsf	EECON1, WREN 		;Enable writes
	bcf	INTCON, GIE 		;Disable INTs.
	btfsc	INTCON, GIE 		;SEE AN576
	goto	$-2
	movlw	0x55 ;
	movwf	EECON2 			;Write 55h
	movlw	0xAA 			;
	movwf	EECON2 			;Write AAh
	bsf	EECON1, WR 		;Set WR bit to begin write

; need to keep checking for EEIF being set (write complete.)
	BANK0
ee_wri	
	btfss	PIR1,EEIF
	goto	ee_wri
	bcf	PIR1,EEIF		; clear the EEIF write complete interupt
	BANK1
	bcf	EECON1, WREN 		;Disable writes

; Program the low byte
	movlw 	DMX_LOW_BYTE			;DMX LOW ADDRESS
	movwf 	EEADR 			;Data Memory Address to write
	BANK0
	movf	newdmxlow,w
	BANK1
#if PROCESSOR == 16F688
	movwf	EEDAT 			;Data Memory Value to write
#endif
	bcf	EECON1, EEPGD 		;Point to DATA memory
	bsf	EECON1, WREN 		;Enable writes
	bcf	INTCON, GIE 		;Disable INTs.
	btfsc	INTCON, GIE 		;SEE AN576
	goto	$-2
	movlw	0x55 			;
	movwf	EECON2 			;Write 55h
	movlw	0xAA 			;
	movwf	EECON2 			;Write AAh
	bsf	EECON1, WR 		;Set WR bit to begin write
; need to keep checking for EEIE being set (write complete.)
	BANK0
ee_wri1
	btfss	PIR1,EEIF
	goto	ee_wri1
	bcf	EECON1, WREN 		;Disable writes
	BANK1
	bcf	PIR1,EEIF		; clear the EEIF write complete interupt

; Program the DMX_STATUS_BYTE as 00
	movlw 	DMX_STATUS_BYTE			;
	movwf 	EEADR 			;Data Memory Address to write
	BANK0
	
	BANK1
	movlw	d'0'
#if PROCESSOR == 16F688
	movwf	EEDAT 			;Data Memory Value to write
#endif
	bcf	EECON1, EEPGD 		;Point to DATA memory
	bsf	EECON1, WREN 		;Enable writes
	bcf	INTCON, GIE 		;Disable INTs.
	btfsc	INTCON, GIE 		;SEE AN576
	goto	$-2
	movlw	0x55 			;
	movwf	EECON2 			;Write 55h
	movlw	0xAA 			;
	movwf	EECON2 			;Write AAh
	bsf	EECON1, WR 		;Set WR bit to begin write
; need to keep checking for EEIE being set (write complete.)
	BANK0
ee_wri2	
	btfss	PIR1,EEIF
	goto	ee_wri2
	bcf	EECON1, WREN 		;Disable writes
	BANK1
	bcf	PIR1,EEIF		; clear the EEIF write complete interupt

	BANK0

; Finished programming, flash green infinitely (ch2 of RGB = G, green means go)
progend
	clrwdt
	bsf 	CH2PORT,CH2PIN			; Turn on Green
	bsf		CH5PORT,CH5PIN
	bsf		CH8PORT,CH8PIN
	call 	loop1sec
	bcf 	CH2PORT,CH2PIN			; Turn off Green
	bcf 	CH5PORT,CH5PIN
	bcf		CH8PORT,CH8PIN
	call 	loop1sec
	goto	progend

; --------- END PROGRAM ROUTINE ---------

; --------- Delay Routines ---------
loop
    movlw   d'250'          ; 250mS
    call    WaitNms
    return

loop1sec
    call loop
    call loop
    call loop
    call loop
    return

WaitNms
    movwf   timer           ; timer = w
loopNms
    movlw   d'248'          ; 250-2 instructions
    call    Wait1Ms
    ;goto    $-1
    decfsz  timer,f
    goto loopNms
    return

Wait1Ms
    movlw   d'255'          ;initial value, check..?
    movwf   count
loop1ms
    decfsz  count,1
    goto    loop1ms
    return

	END
